#ifndef __CXXCALL_TESTS_UTILS__
#define __CXXCALL_TESTS_UTILS__   1

#include <iostream>
#include <cstdlib>
#include <string>
#include <vector>
#include <utility>
#include <tuple>
#include <typeinfo>
#include <sstream>

#define ASSERT(Cond)   \
  do   \
    {   \
      if (!(Cond))   \
        {   \
          std::cerr << "\n\nAssertion failed: " << #Cond <<   \
            "\nLine: " << __LINE__ << "\nFile: " << __FILE__ <<   \
            "\nFunction: " << cur_fname << "\n\n";   \
          exit (EXIT_FAILURE);   \
        }   \
    }   \
  while (false)

#define CONCAT_(x, y)   x ## y
#define CONCAT(x, y)    CONCAT_(x, y)

#define TUPLE   std::make_tuple

static std::string cur_fname;

struct test_fn
{
  std::string msg;
  void (*fct) ();

  test_fn (const std::string& m, void (*f) ()) : msg (m), fct (f)
    {
    }

  void run () const
    {
      std::cout << "Testing " << this->msg << "...";
      this->fct ();
      std::cout << " OK\n";
    }
};

std::vector<test_fn>& test_suite ()
{
  static std::vector<test_fn> ts;
  return (ts);
}

void run_tests ()
{
  for (auto& tst : test_suite ())
    tst.run ();

  std::cout << "Done\n";
}

struct test_module
{
  typedef std::pair<std::string, void (*) ()> pair_type;

  test_module (const char *name, std::initializer_list<pair_type> tests)
    {
      std::string nm = std::string (" (") + name + ") ";
      for (auto pair : tests)
        test_suite().push_back (test_fn (pair.first + nm, pair.second));
    }
};

template <class T>
struct equal
{
  template <class ...Args>
  static bool eq (void *addr, T (*fn) (Args...), Args... args)
    {
      return (*(const T *)addr == fn (args...));
    }
};

template <>
struct equal<void>
{
  static bool eq (void *, ...)
    {
      return (true);
    }
};

static void *global_tuple;

template <class T>
static bool eq_tuples (const T& left)
{
  return (left == *(const T *)global_tuple);
}

template <class ...>
struct sum_type;

template <class T>
struct sum_type<T>
{
  typedef T type;
};

template <class T, class ...Args>
struct sum_type<T, Args...>
{
  typedef typename sum_type<Args...>::type rtype;
  typedef decltype (std::declval<T>() + std::declval<rtype>()) type;
};

template <class T>
T sum (T x)
{
  std::ostringstream ostr;
  ostr << "sum (" << typeid(x).name () << ")";
  cur_fname = ostr.str ();

  ASSERT (eq_tuples (TUPLE (x)));
  return (x);
}

template <class T>
void print_type (std::ostringstream& ostr, T x)
{
  ostr << typeid(x).name () << ", ";
}

template <class T, class ...Args>
auto sum (T x, Args... args) -> typename sum_type<T, Args...>::type
{
  std::ostringstream ostr;
  ostr << "sum (";

  using helper_type = int[];
  helper_type { (print_type (ostr, x), 0), (print_type (ostr, args), 0)... };
  std::string s = ostr.str ();
  s.erase (s.end () - 2, s.end ());
  cur_fname = s + ')';

  ASSERT (eq_tuples (TUPLE (x, args...)));
  return (x + sum (args...));
}

#endif
